package com.jse;

import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;

import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import com.jse.json.Json;
import com.jse.json.JsonArray;

public class Js {
	
	record Jf(String path,Long time,Function<Object,Object> o){};
	
	public static final ScriptEngineManager SEM=new ScriptEngineManager();
	private static ScriptEngine eng;//默认
	private static final HashMap<String,Jf> pathjf=new HashMap<>();
	private static final HashMap<String,Long> pathjs=new HashMap<>();
	static {
		//-scripting -- arg1 arg2 arg3 可跟参数 $ARG[1] echo $ENV $EXEC("ls -l") -scripting
		System.setProperty("nashorn.args","-doe --language=es6 --optimistic-types=[true] --global-per-engine");//使用nashorn引擎时配置的参数
//		System.setProperty("nashorn.debug","true");//nashorn debug
		System.setProperty("jvm.Dtruffle.js.NashornJavaInterop", "true");//graaljs
//		System.setProperty("polyglot.js.strict","true");//严格模式
		System.setProperty("polyglot.log.file","./jse.log");//js的日志
		System.setProperty("polyglot.js.nashorn-compat", "true");//graaljs
		System.setProperty("engine.WarnInterpreterOnly","false");//禁用警告
		System.setProperty("polyglot.js.commonjs-require", "true");//启用commonjs require('./foo.js')
		System.setProperty("polyglot.js.commonjs-require-cwd","D:/test/node_modules");//es模块默认的路径 Jse.conf文件 jse.js.path 注释则默认当前工作目录
		System.setProperty("polyglot.js.commonjs-core-modules-replacements","buffer:buffer/,path:path-browserify");//用于替换全局 Node.js内置的 npm 包
		System.setProperty("python.import.site", "false");//jython /META-INF/es/jnpm.js
		
		if(Lang.isPresent("com.oracle.truffle.js.scriptengine.GraalJSScriptEngine")) {eng=SEM.getEngineByName("js");}
		else if(Lang.isPresent("org.mozilla.javascript.engine.RhinoScriptEngine"))eng=SEM.getEngineByName("rhino");
		else eng=SEM.getEngineByName("nashorn");
		eval(new InputStreamReader(Js.class.getResourceAsStream("/META-INF/jse.js")));
//		jnpm();babel();//延迟加载
	}
	private Js(){}
	
	public static Object put(String name,Object o){return eng.getBindings(ScriptContext.GLOBAL_SCOPE).put(name,o);}
	public static Object get(String name) {return eng.getBindings(ScriptContext.GLOBAL_SCOPE).get(name);}
	public static boolean containsKey(String name) {return eng.getBindings(ScriptContext.GLOBAL_SCOPE).containsKey(name);}
	public static Set<String> keys(){return eng.getBindings(ScriptContext.GLOBAL_SCOPE).keySet();}
	public static Collection<Object> beans(){return eng.getBindings(ScriptContext.GLOBAL_SCOPE).values();}
	public static Set<Entry<String, Object>> entrys(){return eng.getBindings(ScriptContext.GLOBAL_SCOPE).entrySet();}
	public static Object eval(String script){
		try{return eng.eval(script);}catch(ScriptException e){throw new RuntimeException(e);}
	}
	public static Object evalNewGlobal(String script){
		try{return eng.eval("loadWithNewGlobal("+script+")");}catch(ScriptException e){throw new RuntimeException(e);}
	}
	public static Object eval(Reader reader){
		try{return eng.eval(reader);}catch(ScriptException e){
			e.printStackTrace();
			throw new RuntimeException(e);}
	}
	public static Object eval(String script,Map bind){
		if(bind==null)return eval(script);
		try {
			if(bind instanceof Bindings b)return eng.eval(script,b);
			var b=eng.createBindings();b.putAll(bind);
			return eng.eval(script,b);
		} catch (ScriptException e) {
			throw new RuntimeException(e);
		}
	}
	public static Object eval(Path path,Map bind){
		return eval(Fs.readString(path),bind);
	}
	public static Object invokeMethod(Object o,String name,Object...args){
		try {
			return ((Invocable)eng).invokeMethod(o, name, args);
		} catch (NoSuchMethodException | ScriptException e) {
			throw new RuntimeException(e);
		}
	}
	public static Object invokeFunction(String name,Object...args){
		try {
			return ((Invocable)eng).invokeFunction(name, args);
		} catch (NoSuchMethodException | ScriptException e) {
			throw new RuntimeException(e);
		}
	}
	public static <T> T getInterface(Object thiz,Class<T> c){
		return ((Invocable)eng).getInterface(thiz, c);
	}
	public static <T> T getObject(String name,Class<T> c){
		return ((Invocable)eng).getInterface(eval(name), c);
	}
	public static CompiledScript compile(String script){
		try {
			return ((Compilable)eng).compile(script);
		} catch (ScriptException e) {
			throw new RuntimeException(e);
		}
	}
	public static JsonArray parseCode(String code){
		String x=(String)invokeFunction("parsecode",code);
		return Json.parse(x,JsonArray.class);
	}
	public static void jnpm() {eval(new InputStreamReader(Js.class.getResourceAsStream("/META-INF/jse/jnpm.js")));}
	public static void babel() {eval(new InputStreamReader(Js.class.getResourceAsStream("/META-INF/jse/babel.min.js")));}
	
	public static Object function(String path,String name,Object...args) throws NoSuchMethodException, ScriptException {
		Long o=(Long)pathjs.get(path);
		var p=Path.of(path);
		if(o==null) {
			long t=Fs.lastModified(p);
			if(t!=o.longValue()) {
				eval(Fs.readString(p));pathjs.put(path,t);
			}
		}
		return ((Invocable)eng).invokeFunction(name,args);
	}
	public static Object run(String path,Object x) throws NoSuchMethodException, ScriptException {
		Jf jf=pathjf.get(path);
		boolean isOverride=jf==null;
		long currtime=isOverride?0:Fs.lastModified(path);//当前文件时间;
		if(isOverride||currtime!=jf.time) {
				String code=Fs.readString(path);
				var cs=compile(code);
				var o=cs.eval();
				if(o==null)o=cs.getEngine().get("main");//非自解析函数走main兼容
				currtime=(currtime==0)?Fs.lastModified(path):currtime;
				jf=new Jf(path,currtime,Js.getInterface(o,Function.class));
				pathjf.put(path,jf);
		}
		return jf.o.apply(x);
	}
	public static Object evalVue(String path,Map<String,Object> tbl) throws NoSuchMethodException, ScriptException {
		var p=Path.of(path);
			String code=Fs.readString(p);
			int s1=code.indexOf("//@jse");
			if(s1==-1) {
				return code;
			}
			int s2=code.lastIndexOf("//@jse");
			String x1=code.substring(0,s1);
			String x2=code.substring(s1,s2);
			String x3=code.substring(s2+6);
			eval(x2);
			Object obj=((Invocable)eng).invokeFunction("JSE",tbl);
			return x1+"var JSE="+Json.toJson(obj)+x3;
	}
	
	
	public static void main(String[] args) throws NoSuchMethodException, FileNotFoundException, ScriptException {
		
//		var bind=new SimpleBindings(Lang.ofMap("o",2,"x",3));
//		var bind1=Map.of("o",1);
//		js.fun("C:\\Users\\Administrator\\Desktop\\vue\\min.js",null,"main",8);
//		js.fun("C:\\Users\\Administrator\\Desktop\\vue\\min.js",bind,"main",7);
//		var x=js.fun("C:\\Users\\Administrator\\Desktop\\vue\\min.js",bind1,"main",6);
//		Js.function("C:\\Users\\Administrator\\Desktop\\vue\\min.js","main",5);
//		Js.function("C:\\Users\\Administrator\\Desktop\\vue\\min.js","main",4);
//		System.err.println(x);
//		ScriptEngine eng = new ScriptEngineManager()
//			    .getEngineByName("js");
//			Object fn = eng.eval("(function() { return this; })");
//			Invocable inv = (Invocable) eng;
//			Object result = inv.invokeMethod(fn, "call", fn);
//			System.out.println(result);
		
//		var py=new Js(sem.getEngineByName("python"));
//		Object a=py.eval("""
//				def hello() :
//					li=['a', 'b', 'mpilgrim', 'z', 'example']
//					return "返回："+li[-2]
//				hello()
//				""");
//		System.out.println(a);
//		System.out.println(keys());
//		System.out.println(eval("""
//				load('D:/work/nw/web/assets/vue/vue.runtime.global.prod.js')
//				//load('D:/work/nw/web/assets/vue/server-renderer.esm-browser.prod.js')
//				
//				"""));
//		Ioc.put("a", 123);
//		System.out.println(Js.eval("a"));
		long s1=System.currentTimeMillis();
//		for (int i = 0; i < 1000; i++) {
//			Js.fun("D:\\work\\xjy\\src\\test.js", null,"main",1);
//		}
		long s2=System.currentTimeMillis();
		
		long s3=System.currentTimeMillis();
//		String s=Fs.readString("D:\\work\\xjy\\src\\test2.js");
		for (int i = 0; i < 1; i++) {
			//Js.run("D:\\work\\xjy\\src\\test.js",1);
			//Js.run("D:\\work\\xjy\\src\\test2.js",2);
		}
		long s4=System.currentTimeMillis();
		System.out.println((s2-s1)+"__"+(s4-s3));
//		long o=Long.valueOf(1000L);
//		Long t=Long.parseLong("1000");
//		System.out.println(o==t);
		var xlog=Log.get("xx");
		var obj=Js.eval("""
				//"nashorn callsite trace enterexit"; // equivalent to -tcs=enterexit
    //"nashorn callsite trace miss";      // equivalent to -tcs=miss
    //"nashorn callsite trace objects"; 
				"nashorn callsite profile";
				  // equivalent to -tcs=objects
var File = Java.type("java.io.File")
var listHomeDir = new File("D:/work/camh/web").listFiles()
 
// Java array to JavaScript array conversion by Java.from
var jsListHome = Java.from(listHomeDir)
print(jsListHome)
var jpegModifiedDates = jsListHome.filter(function(val) { return val.getName().endsWith(".html") })
    .map(function(val) { return val })
    print(jpegModifiedDates)
    print(Java.from(List.of(1,2,3,4)).filter((x)=>{return x<3;}).map((a)=>{return a+2;}))
    print([1,2,3,4].filter((x)=>{return x<3;}).map((a)=>{return a;}))
    var jString = Java.type("java.lang.String")
    print(jString==java.lang.String.class.static)//or java.lang.String
    var ArrayListExtender = Java.extend(ArrayList)
var printSizeInvokedArrayList = new ArrayListExtender() {
    size: function() { print("size invoked!"); }
}
var a0=new ArrayList();
var a = new ArrayListExtender() {
    add: function(x, y) {
      if(typeof(y) === "undefined") {
          print("add(e) invoked!");
      } else {
          print("add(i, e) invoked!");
      }
    },
    toString: function(){return Java.super(a).toString();}
}
var b=Java.super(a)
a.add(1)//扩展后的
b.add(22)//原始的a，真正的add了
print(a)
print(a0)
//多线程和同步
var Thread = Java.type("java.lang.Thread");
var sum = 0;
var lock = {};
 
function run() {
    Thread.sleep(Math.floor(Math.random()*700) + 100);
 
    // create synchronized wrapper of given function
    // and use the second param as the synchronization lock
    Java.synchronized(function() sum++, lock)();
}
/*
var threads = [];
for (var i = 0; i < 4; i++) {
    var t = new Thread(run);
    threads.push(t);
    t.start();
}
 
for (var i in threads) {
   threads[i].join();
}
 */
// always prints 4
print(sum);
//Java.asJSONCompatible
//JSON.stringify({ x: 343, y: 'hello', z: [2,4, 5],a:Java.from(a)})
var pp={ x: 343, y:'hello', z: [2,4, 5],a}
var obj = new JSAdapter() {
	__call__: function(name, arg1, arg2) {
        print("method '" + name + "' called with " + arg1 + ", " + arg2);
    },
}
if (!Object.assign) {
            Object.defineProperty(Object, "assign", {
                enumerable: false,
                configurable: true,
                writable: true,
                value: function (target, firstSource) {
                    "use strict";
                    if (target === undefined || target === null)
                        throw new TypeError("Cannot convert first argument to object");
                    var to = Object(target);
                    for (var i = 1; i < arguments.length; i++) {
                        var nextSource = arguments[i];
                        if (nextSource === undefined || nextSource === null) continue;
                        var keysArray = Object.keys(Object(nextSource));
                        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
                            var nextKey = keysArray[nextIndex];
                            var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
                            if (desc !== undefined && desc.enumerable) to[nextKey] = nextSource[nextKey];
                        }
                    }
                    return to;
                }
            });
        }
Object.is = function(x, y) {
    if (x === y) {
      // 1/+0 = +Infinity， 1/-0 = -Infinity, +Infinity不等于-Infinity
      // Infinity 属性用于存放表示正无穷大的数值。负无穷大是表示负无穷大一个数字值。
      return x !== 0 || 1 / x === 1 / y;
    } 
      // 一个变量不等于自身变量,那么它一定是 NaN
      // 两个都是NaN的时候返回true
      return x !== x && y !== y;
};
pp=Object.assign(obj,pp)
pp.func("hello", "world");
//Object.is(1,1)


/*
let handler = {
    get1: (object, prop) => {
        console.log(`Hi ${object.firstName} ${object.lastName}`)
        //return object[prop]
    }
}*/
Object.keys = function(obj) {
    let res = [];
    for (let name in obj) {
        if (obj.hasOwnProperty(name)) res.push(name);
    }
    return res;
}
function clone(myObj){  
    if(typeof(myObj) != 'object' || myObj == null) return myObj;  
    var newObj = new Object();  
    for(var i in myObj){  
      newObj[i] = clone(myObj[i]); 
    }  
    return newObj;  
}
/*代理实现类*/
function Proxy(target,handle){
  var targetCopy = clone(target);
  Object.keys(targetCopy).forEach(function(key){
    Object.defineProperty(targetCopy, key, {
      get: function() {
        return handle.get && handle.get(target,key);
      },
      set: function(newVal) {
        handle.set && handle.set();
        target[key] = newVal;
      }
    });
  })
  return targetCopy;
}
var target1 = {
    firstName: "月",
    lastName: "弦",
    name: "月",
    age: 152
}
var handler1 = {

}
let proxyExample = new Proxy(target1, handler1);

print(proxyExample.age) // "Hi 月 弦"

proxyExample.name = "Dave";

console.log(proxyExample.name); // Dave
console.log(target1.name); // Dave
var mm={};
Object.bindProperties(mm,xlog);
print(mm.name)
//print($ENV["JAVA_HOME"])
//$OUT=$EXEC("dir")//ipconfig
//notepad $OUT=string
//print("-scripting = " + $OPTIONS._scripting);
//print("--compile-only = " + $OPTIONS._compile_only);
//print("-timezone = " + $OPTIONS._timezone.ID);
//print("op = " +JSON.stringify($OPTIONS));
//$OUT=readFully("/1.txt")
//var name = readLine("What is your name? ")
//print($OUT)

				""",Map.of("xlog",xlog));
//		var x=Stream.of(new File("D:/work/camh/web").listFiles()).filter((v)->v.getName().endsWith(".html"))
//				.map((v)->v);
//		var x1=java.util.stream.Stream.of(1,2,3,4).limit(3).map((a)->{return a;});
//		System.out.println(x1);
//		System.out.println(((Map)obj).get("z"));
//		com.jse.nashorn.api.scripting.ScriptObjectMirror som=null;
		//"".getClass().getName().endsWith("nashorn.api.scripting.ScriptObjectMirror");
//		som.values()
		var cs=compile("()=>{}");
//		cs.eval();
		System.out.println(cs.eval());
	}

	

	
}
